﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace LogAnalyzer
{
    public partial class MainForm : Form
    {

        private List<LogLine> _fileContents;
        private List<LogLineDefinition> _definitions = new List<LogLineDefinition>();
        LogLineDefinition _selectedDefinition;
        string _clipboardText;

        private Dictionary<ToolStripButton, LogLineDefinition> _defButtons = new Dictionary<ToolStripButton, LogLineDefinition>();
        public MainForm()
        {
            InitializeComponent();
            this.Text = "Log Analyzer v" + Assembly.GetExecutingAssembly().GetName().Version.ToString();
            foreach (var file in Directory.GetFiles(Environment.CurrentDirectory, "*.xml"))
            {
                XmlSerializer s = new XmlSerializer(typeof(LogLineDefinition));
                using (var r = new StreamReader(file))
                {
                    LogLineDefinition d = (LogLineDefinition)s.Deserialize(r);
                    _definitions.Add(d);
                }
            }
            foreach (var definition in _definitions)
            {
                ToolStripButton b = new ToolStripButton();
                b.DisplayStyle = ToolStripItemDisplayStyle.Text;
                b.Text = definition.Name;
                b.CheckOnClick = true;
                b.CheckedChanged += new EventHandler(DefinitionCheckedChanged);
                _defButtons[b] = definition;
                _tools.Items.Add(b);
            }
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (keyData == Keys.Enter)
            {
                return true;
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }


        void DefinitionCheckedChanged(object sender, EventArgs e)
        {
            ToolStripButton b = (ToolStripButton)sender;
            if (!b.Checked)
                return;
            _selectedDefinition = _defButtons[b];
            foreach (var button in _defButtons.Keys.Where(o => o != b))
            {
                button.Checked = false;
            }
            if (!string.IsNullOrEmpty(_dlgOpenFile.FileName))
            {
                LoadLogFromFile(_dlgOpenFile.FileName);
            }
            else if (!string.IsNullOrEmpty(_clipboardText))
            {
                LoadLogFromString(_clipboardText);
            }

        }

        private void OpenFile(object sender, EventArgs e)
        {
            if (_dlgOpenFile.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                LoadLogFromFile(_dlgOpenFile.FileName);
            }
        }

        void LoadLogFromFile(string fileName)
        {
            if (_selectedDefinition == null)
            {
                _lblNoLogType.Visible = true;
                return;
            }
            var length = (new FileInfo(fileName)).Length;
            if (length > 100 * 1024 * 1024)
            {
                MessageBox.Show("File is over 100MB large. Can not parse");
                return;
            }
            if (length > 20 * 1024 * 1024 &&
                MessageBox.Show("File is too large, are you sure you want to continue?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) != System.Windows.Forms.DialogResult.Yes)
            {
                return;
            }

            this.Text = "Log Analyzer v" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + " - " + Path.GetFileName(fileName);
            _lblFileName.Text = fileName;
            try
            {
                Cursor = Cursors.WaitCursor;
                _tsbLoading.Visible = true;
                _lblNoLogType.Visible = false;
                _tsbLoading.Value = 0;
                Application.DoEvents();
                _listView.BeginUpdate();
                _listView.Items.Clear();
                _listView.Columns.Clear();
                _tsbLoading.Minimum = 0;
                _tsbLoading.Maximum = (int)length;
                _fileContents = new List<LogLine>();
                using (FileStream s = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 1000, FileOptions.Asynchronous))
                using (TextReader r = new StreamReader(s))
                {
                    ReadContent(r, length, () => s.Position);
                }
                _listView.Columns.Add("#", 60);
                for (int i = 0; i < _selectedDefinition.Columns.Length; i++)
                {
                    _listView.Columns.Add(_selectedDefinition.Columns[i].Caption, _selectedDefinition.Columns[i].Size);
                }
                _listView.VirtualMode = true;
                _listView.VirtualListSize = _fileContents.Count;
                _listView.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(_listView_RetrieveVirtualItem);
            }
            finally
            {
                Cursor = Cursors.Arrow;
                _tsbLoading.Visible = false;
                _listView.EndUpdate();
            }
        }


        void LoadLogFromString(string content)
        {
            if (_selectedDefinition == null)
            {
                _lblNoLogType.Visible = true;
                return;
            }
            _dlgOpenFile.FileName = null;

            this.Text = "Log Analyzer v" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + " - <clipboard>";
            _lblFileName.Text = "<clipboard>";
            try
            {
                Cursor = Cursors.WaitCursor;
                _tsbLoading.Visible = true;
                _lblNoLogType.Visible = false;
                _tsbLoading.Value = 0;
                Application.DoEvents();
                _listView.BeginUpdate();
                _listView.Items.Clear();
                _listView.Columns.Clear();
                _tsbLoading.Minimum = 0;
                _tsbLoading.Maximum = content.Length;
                _fileContents = new List<LogLine>();
                using (TextReader r = new StringReader(content))
                {
                    ReadContent(r, content.Length, () => 0);
                }
                _listView.Columns.Add("#", 60);
                for (int i = 0; i < _selectedDefinition.Columns.Length; i++)
                {
                    _listView.Columns.Add(_selectedDefinition.Columns[i].Caption, _selectedDefinition.Columns[i].Size);
                }
                _listView.VirtualMode = true;
                _listView.VirtualListSize = _fileContents.Count;
                _listView.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(_listView_RetrieveVirtualItem);
            }
            finally
            {
                Cursor = Cursors.Arrow;
                _tsbLoading.Visible = false;
                _listView.EndUpdate();
            }
        }

        private int ReadContent(TextReader r, long length, Func<long> getCurrentPosition)
        {
            int lineCount = 1;
            bool skipped = false;
            string line = r.ReadLine();
            while (line != null)
            {
                UpdateProgressBar((int)length, (int)getCurrentPosition());
                var match = _selectedDefinition.RegularExpression.Match(line);
                var styleMatch = _selectedDefinition.Styles.FirstOrDefault(o => o.IsMatch(line));
                if (match.Success && (styleMatch == null || !styleMatch.StyleDefition.HideLine))
                {
                    if (match.Success && match.Groups.Count - 1 >= _selectedDefinition.Columns.Length)
                    {
                        var content = MakeArray(lineCount, match.Groups.OfType<Capture>().Skip(1).Select(o => o.Value.Trim()).ToArray());
                        _fileContents.Add(new LogLine()
                        {
                            Contents = content,
                            StyleDefinition = styleMatch != null ? styleMatch.StyleDefition : null,
                            OriginalLine = line
                        });
                    }
                    skipped = false;
                }
                else if (!match.Success && _fileContents.Count > 0 && !skipped)
                {
                    var last = _fileContents.Last();
                    switch (_selectedDefinition.NonMatchedLogLineStrategy)
                    {
                        case NonMatchedLogLineStrategy.None:
                        case NonMatchedLogLineStrategy.AppendToPrevious:
                            last.Contents[_selectedDefinition.Columns.Length] += " | " + line;
                            break;
                        case NonMatchedLogLineStrategy.AddAsNewLine:
                            var content = new string[_selectedDefinition.Columns.Length + 1];
                            content[0] = lineCount.ToString();
                            content[_selectedDefinition.Columns.Length] = line;
                            _fileContents.Add(new LogLine()
                            {
                                Contents = content,
                                StyleDefinition = last.StyleDefinition,
                                OriginalLine = line
                            });
                            break;
                        default:
                            break;
                    }
                    skipped = false;
                }
                else
                {
                    skipped = true;
                }
                lineCount++;
                line = r.ReadLine();
            }
            return lineCount;
        }

        private string[] MakeArray(int line, string[] p)
        {
            string[] result = new string[p.Length + 1];
            result[0] = line.ToString();
            p.CopyTo(result, 1);
            return result;
        }


        private void UpdateProgressBar(int length, int current)
        {
            var statBarPos = _tsbLoading.Value;
            int increment = length / 20;
            if (current - statBarPos > increment)
            {
                _tsbLoading.Value = current;
                _tsbLoading.Invalidate();
                _tools.Update();
            }
        }

        void _listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            if (e.ItemIndex >= _fileContents.Count)
                return;
            e.Item = new ListViewItem(_fileContents[e.ItemIndex].Contents);
            e.Item.Tag = _fileContents[e.ItemIndex];
            var definition = _fileContents[e.ItemIndex].StyleDefinition;
            if (definition != null)
            {
                if (definition.BackColor != null)
                    e.Item.BackColor = definition.BackColor.Value;
                if (definition.ForeColor != null)
                    e.Item.ForeColor = definition.ForeColor.Value;
                if (definition.FontStyle != FontStyle.Regular)
                {
                    if (!_fonts.ContainsKey(definition.FontStyle))
                        _fonts[definition.FontStyle] = new System.Drawing.Font(e.Item.Font, definition.FontStyle);
                    e.Item.Font = _fonts[definition.FontStyle];
                }
            }
        }

        private Dictionary<FontStyle, Font> _fonts = new Dictionary<FontStyle, Font>();

        private void _listView_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.C && e.Control)
            {
                if (_listView.SelectedIndices.Count > 0)
                {
                    StringBuilder b = new StringBuilder();
                    foreach (int index in _listView.SelectedIndices)
                    {
                        var l = _fileContents[index];
                        b.AppendLine(l.OriginalLine);
                    }
                    Clipboard.SetText(b.ToString());
                }
                else if (_listView.FocusedItem != null)
                {
                    LogLine l = _listView.FocusedItem.Tag as LogLine;
                    if (l != null)
                        Clipboard.SetText(l.OriginalLine);
                }
                
            }

        }

        private string _lastSearchText;
        private void _btnSearch_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(_txtSearch.Text) || _fileContents == null || _fileContents.Count == 0)
            {
                return;
            }

            if (_txtSearch.Text != _lastSearchText)
            {
                _lastSearchText = null;
            }
            var startIndex = 0;
            if (_listView.SelectedIndices.Count > 0)
                startIndex = _listView.SelectedIndices[0];
            _lastSearchText = _txtSearch.Text;
            var item = _fileContents.Skip(startIndex + 1).FirstOrDefault(o => o.OriginalLine != null && o.OriginalLine.IndexOf(_lastSearchText, StringComparison.CurrentCultureIgnoreCase) >= 0);
            if (item == null)
                return;
            startIndex = _fileContents.IndexOf(item);
            _listView.EnsureVisible(startIndex);
            _listView.SelectedIndices.Clear();
            _listView.SelectedIndices.Add(startIndex);
        }

        private void _listView_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button != System.Windows.Forms.MouseButtons.Right)
                return;
            var hitInfo = _listView.HitTest(e.Location);
            if (hitInfo.SubItem == null)
                return;
            _menuCopy.Tag = hitInfo;
            _menuCopy.Show(e.Location);
        }

        private void _miCopyCell_Click(object sender, EventArgs e)
        {
            if (_menuCopy.Tag == null)
                return;
            ListViewHitTestInfo item = (ListViewHitTestInfo)_menuCopy.Tag;
            if(item.SubItem != null)
                Clipboard.SetText(item.SubItem.Text);
        }

        private void _miCopyRow_Click(object sender, EventArgs e)
        {
            if (_menuCopy.Tag == null)
                return;
            ListViewHitTestInfo item = (ListViewHitTestInfo)_menuCopy.Tag;
            if (item.Item == null)
                return;
            LogLine l = item.Item.Tag as LogLine;
            if (l != null)
                Clipboard.SetText(l.OriginalLine);
        }

        private void _txtSearch_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                _btnSearch.PerformClick();
                e.Handled = true;
            }
        }

        bool _goToLineTextChanged;
        private void _txtLineNumber_Go(object sender, EventArgs e)
        {
            if(_goToLineTextChanged)
                GoToLine();
        }

        private void _txtLineNumber_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.Enter)
                return;
            this.Validate(true);
            GoToLine();
        }

        private void GoToLine()
        {
            int lineNo;
            if (_fileContents == null || _fileContents.Count == 0)
                return;
            if (int.TryParse(_txtLineNumber.Text, out lineNo))
            {
                var lineNoText = lineNo.ToString();
                var item = _fileContents.FirstOrDefault(o => o.Contents[0] == lineNoText);
                if (item == null)
                    return;
                var i = _fileContents.IndexOf(item);
                _listView.EnsureVisible(i);
                _listView.SelectedIndices.Clear();
                _listView.SelectedIndices.Add(i);
            }
            _goToLineTextChanged = false;
        }

        private void _txtLineNumber_TextChanged(object sender, EventArgs e)
        {
            _goToLineTextChanged = true;
        }

        private void PasteLog(object sender, EventArgs e)
        {
            if (!Clipboard.ContainsText())
                return;
            _clipboardText = Clipboard.GetText();
            LoadLogFromString(_clipboardText);
        }


    }

    public class LogLineDefinition
    {

        public string Name { get; set; }

        public ColumnDefinition[] Columns { get; set; }

        public string RegularExpressionString
        {
            get { return _regularExpression; }
            set
            {
                if (_regularExpression == value)
                    return;
                _regularExpression = value;
                _regex = new Regex(value);
            }
        }
        private string _regularExpression;

        [XmlIgnore]
        public Regex RegularExpression
        {
            get
            {
                return _regex;
            }
        }
        private Regex _regex;

        public StyleExpression[] Styles { get; set; }

        public NonMatchedLogLineStrategy NonMatchedLogLineStrategy { get; set; }

    }

    public class ColumnDefinition
    {
        public string Caption { get; set; }

        public int Size { get; set; }
    }

    public class StyleExpression
    {
        public string RegularExpressionString
        {
            get { return _regularExpression; }
            set
            {
                if (_regularExpression == value)
                    return;
                _regularExpression = value;
                _regex = new Regex(value);
            }
        }
        private string _regularExpression;

        [XmlIgnore]
        public Regex RegularExpression
        {
            get
            {
                return _regex;
            }
        }
        private Regex _regex;

        public bool NegateRegularExpression { get; set; }

        public StyleDefinition StyleDefition { get; set; }

        public bool IsMatch(string line)
        {
            var match = RegularExpression.IsMatch(line);
            if (NegateRegularExpression)
                return !match;
            else
                return match;

        }
    }

    public class StyleDefinition
    {
        [XmlAttribute]
        public bool HideLine { get; set; }

        [XmlAttribute]
        public string BackColorName
        {
            get { return _BackColorName; }
            set
            {
                _BackColorName = value;
                BackColor = Color.FromName(value);
            }
        }
        private string _BackColorName;

        [XmlAttribute]
        public string ForeColorName
        {
            get { return _ForeColorName; }
            set
            {
                _ForeColorName = value;
                ForeColor = Color.FromName(value);
            }
        }
        private string _ForeColorName;

        [XmlIgnore]
        public Color? BackColor { get; set; }

        [XmlIgnore]
        public Color? ForeColor { get; set; }

        [XmlAttribute]
        public FontStyle FontStyle { get; set; }

        public StyleDefinition()
        {
            HideLine = false;
            FontStyle = System.Drawing.FontStyle.Regular;
        }
    }

    public enum NonMatchedLogLineStrategy
    {
        None = 0,
        Ignore = 0,
        AppendToPrevious,
        AddAsNewLine
    }

    public class LogLine
    {

        public string OriginalLine { get; set; }

        public string[] Contents { get; set; }

        public StyleDefinition StyleDefinition { get; set; }
    }

}
